fontset = special1, "New York", 12, L2, nowordwrap;
fontset = special2, "New York", 12, L2, center, nowordwrap;
fontset = special3, "New York", 12, L2, right, nowordwrap;
fontset = special4, "New York", 12, L2, nowordwrap;
fontset = special5, "New York", 12, L2, nowordwrap;]
:[font = title; inactive; ]
The Smoothing Parameter
in Music Kit Envelopes
:[font = subsubtitle; inactive; ]
by Julius O. Smith
:[font = text; inactive; center; ]
NeXT Inc.
July 11, 1991
:[font = section; inactive; startGroup; ]
Introduction
:[font = subsection; inactive; startGroup; ]
Definition of an Envelope
:[font = text; inactive; endGroup; ]
A Music Kit "envelope" is a function of time specified by a list of "breakpoints". Each "breakpoint" is specified by two or three numbers. In the two-number case, you specify the time of the breakpoint and desired value of the envelope at that point. The envelope itself is a piecewise exponential function. The desired value at each breakpoint is approached exponentially from the previous breakpoint. Exponential functions are discussed below.
:[font = subsection; inactive; startGroup; ]
Definition of the Envelope Smoothing Parameter
:[font = text; inactive; ]
The optional third parameter in a breakpoint specification is called "smoothness". It controls the relaxation time-constant used in approaching each "target value" in the envelope. The actual definition is the following:
:[font = subsubtitle; inactive; ]
Smoothness is defined as the number of repetitions of the time interval to the next breakpoint needed to reach that breakpoint's value.
:[font = text; inactive; endGroup; endGroup; ]
Thus, the most natural choice of smoothness S is S=1. This means the envelope will just about reach the next envelope breakpoint value at the time specified for that breakpoint. Setting S=0 means the envelope will instantly jump to the next breakpoint. Setting S=2 means the envelope will about reach the next breakpoint value at two times the distance to the next breakpoint; this is "smoother" than S=1. It also means that using values of S larger than 1 means setting breakpoint target values which are larger (when going up) than you actually expect to reach. In fact, if there were no maximum number limit on the DSP, you could take S to infinity and get a piecewise linear envelope.
:[font = section; inactive; startGroup; ]
Exponential Envelopes
:[font = subsection; inactive; startGroup; ]
The Exponential Decay
:[font = text; inactive; ]
First lets look at an exponential decay. This is like the end of an envelope when it is decaying to zero.
:[font = input; ]
Clear[f1,A,tau,t]; (* Clear any previous definitions *)
f1[t_] := A Exp[-t/tau]; (* tau = exponential time constant *)
:[font = text; inactive; ]
For t>0, f1[t] is the function used for the final decay of every envelope, where A is the value of the envelope when the decay starts (nominally the value of the last nonzero breakpoint)..
[(Decay by 3 time constants)] 0.5 0.62428 0 -1 Msboxa
[(2)] 0.23545 -0.0125 0 1 Msboxa
[(4)] 0.44709 -0.0125 0 1 Msboxa
[(6)] 0.65873 -0.0125 0 1 Msboxa
[(8)] 0.87037 -0.0125 0 1 Msboxa
[(time\(sec\))] 1.00625 0 -1 0 Msboxa
[(0.2)] 0.01131 0.12361 1 0 Msboxa
[(0.4)] 0.01131 0.24721 1 0 Msboxa
[(0.6)] 0.01131 0.37082 1 0 Msboxa
[(0.8)] 0.01131 0.49443 1 0 Msboxa
[(1)] 0.01131 0.61803 1 0 Msboxa
[()] 0.02381 0.62428 0 -1 Msboxa
[ -0.001 -0.00725 0 0 ]
[ 1.001 0.61903 0 0 ]
] MathScale
% Start of Graphics
1 setlinecap
1 setlinejoin
newpath
%%Object: Graphics
[ ] 0 setdash
0 setgray
0 setgray
[(Decay by 3 time constants)] 0.5 0.62428 0 -1 Mshowa
gsave
gsave
0.002 setlinewidth
0 0 moveto
1 0 lineto
stroke
0.23545 -0.00625 moveto
0.23545 0.00625 lineto
stroke
0 setgray
[(2)] 0.23545 -0.0125 0 1 Mshowa
0.44709 -0.00625 moveto
0.44709 0.00625 lineto
stroke
0 setgray
[(4)] 0.44709 -0.0125 0 1 Mshowa
0.65873 -0.00625 moveto
0.65873 0.00625 lineto
stroke
0 setgray
[(6)] 0.65873 -0.0125 0 1 Mshowa
0.87037 -0.00625 moveto
0.87037 0.00625 lineto
stroke
0 setgray
[(8)] 0.87037 -0.0125 0 1 Mshowa
0 setgray
[(time\(sec\))] 1.00625 0 -1 0 Mshowa
0.02381 0 moveto
0.02381 0.61803 lineto
stroke
0.01756 0.12361 moveto
0.03006 0.12361 lineto
stroke
0 setgray
[(0.2)] 0.01131 0.12361 1 0 Mshowa
0.01756 0.24721 moveto
0.03006 0.24721 lineto
stroke
0 setgray
[(0.4)] 0.01131 0.24721 1 0 Mshowa
0.01756 0.37082 moveto
0.03006 0.37082 lineto
stroke
0 setgray
[(0.6)] 0.01131 0.37082 1 0 Mshowa
0.01756 0.49443 moveto
0.03006 0.49443 lineto
stroke
0 setgray
[(0.8)] 0.01131 0.49443 1 0 Mshowa
0.01756 0.61803 moveto
0.03006 0.61803 lineto
stroke
0 setgray
[(1)] 0.01131 0.61803 1 0 Mshowa
0 setgray
[()] 0.02381 0.62428 0 -1 Mshowa
grestore
grestore
0 0 moveto
1 0 lineto
1 0.618034 lineto
0 0.618034 lineto
closepath
clip
newpath
0 setgray
gsave
gsave
0.004 setlinewidth
0.02381 0.61803 moveto
0.06349 0.54541 lineto
0.10317 0.48133 lineto
0.14286 0.42477 lineto
0.18254 0.37486 lineto
0.22222 0.33081 lineto
0.2619 0.29194 lineto
0.30159 0.25763 lineto
0.34127 0.22736 lineto
0.38095 0.20065 lineto
0.42063 0.17707 lineto
0.46032 0.15626 lineto
0.5 0.1379 lineto
0.53968 0.1217 lineto
0.57937 0.1074 lineto
0.61905 0.09478 lineto
0.65873 0.08364 lineto
0.69841 0.07381 lineto
0.7381 0.06514 lineto
0.77778 0.05749 lineto
0.81746 0.05073 lineto
0.85714 0.04477 lineto
0.89683 0.03951 lineto
0.93651 0.03487 lineto
0.97619 0.03077 lineto
stroke
grestore
grestore
% End of Graphics
MathPictureEnd
:[font = subsection; inactive; startGroup; ]
T60
:[font = text; inactive; ]
In audio, a reasonable definition of the end of an exponential envelope is when the final decay drops the level by 60 decibels (dB). This decay time for exponentials is called t60 in the reverberation literature. Since Amp_dB = 20 Log[10,Amp], this means we have the formula
:[font = subsubsection; inactive; startGroup; ]
Derivation of t60
:[font = input; startGroup; ]
Clear[A,tau,t60];
-60 == 20 Log[10,f1[t60]/A]
:[font = output; inactive; output; endGroup; ]
-60 == (20*Log[E^(-(t60/tau))])/Log[10]
;[o]
-(t60/tau)
20 Log[E ]
-60 == -------------------
Log[10]
:[font = text; inactive; ]
We divide by A because we are interested in the decay factor, not the absolute final value. Solving the above equation for t60 can be done as follows. First divide both sides by 20, then raise 10 to the value on each side to get
:[font = input; startGroup; ]
0.001 == f1[t60]/A
:[font = output; inactive; output; endGroup; ]
0.001 == E^(-(t60/tau))
;[o]
-(t60/tau)
0.001 == E
:[font = input; startGroup; ]
Solve[%,t60]
:[font = message; inactive; ]
Solve::ifun:
Warning: inverse functions are being used by Solve,
[(Exponential approach to A=1)] 0.5 0.62428 0 -1 Msboxa
[(5)] 0.25352 -0.0125 0 1 Msboxa
[(10)] 0.48323 -0.0125 0 1 Msboxa
[(15)] 0.71294 -0.0125 0 1 Msboxa
[(20)] 0.94265 -0.0125 0 1 Msboxa
[(time\(sec\))] 1.00625 0 -1 0 Msboxa
[(0.2)] 0.01131 0.12361 1 0 Msboxa
[(0.4)] 0.01131 0.24721 1 0 Msboxa
[(0.6)] 0.01131 0.37082 1 0 Msboxa
[(0.8)] 0.01131 0.49443 1 0 Msboxa
[(1)] 0.01131 0.61803 1 0 Msboxa
[()] 0.02381 0.62428 0 -1 Msboxa
[ -0.001 -0.00725 0 0 ]
[ 1.001 0.61903 0 0 ]
] MathScale
% Start of Graphics
1 setlinecap
1 setlinejoin
newpath
%%Object: Graphics
[ ] 0 setdash
0 setgray
0 setgray
[(Exponential approach to A=1)] 0.5 0.62428 0 -1 Mshowa
gsave
gsave
0.002 setlinewidth
0 0 moveto
1 0 lineto
stroke
0.25352 -0.00625 moveto
0.25352 0.00625 lineto
stroke
0 setgray
[(5)] 0.25352 -0.0125 0 1 Mshowa
0.48323 -0.00625 moveto
0.48323 0.00625 lineto
stroke
0 setgray
[(10)] 0.48323 -0.0125 0 1 Mshowa
0.71294 -0.00625 moveto
0.71294 0.00625 lineto
stroke
0 setgray
[(15)] 0.71294 -0.0125 0 1 Mshowa
0.94265 -0.00625 moveto
0.94265 0.00625 lineto
stroke
0 setgray
[(20)] 0.94265 -0.0125 0 1 Mshowa
0 setgray
[(time\(sec\))] 1.00625 0 -1 0 Mshowa
0.02381 0 moveto
0.02381 0.61803 lineto
stroke
0.01756 0.12361 moveto
0.03006 0.12361 lineto
stroke
0 setgray
[(0.2)] 0.01131 0.12361 1 0 Mshowa
0.01756 0.24721 moveto
0.03006 0.24721 lineto
stroke
0 setgray
[(0.4)] 0.01131 0.24721 1 0 Mshowa
0.01756 0.37082 moveto
0.03006 0.37082 lineto
stroke
0 setgray
[(0.6)] 0.01131 0.37082 1 0 Mshowa
0.01756 0.49443 moveto
0.03006 0.49443 lineto
stroke
0 setgray
[(0.8)] 0.01131 0.49443 1 0 Mshowa
0.01756 0.61803 moveto
0.03006 0.61803 lineto
stroke
0 setgray
[(1)] 0.01131 0.61803 1 0 Mshowa
0 setgray
[()] 0.02381 0.62428 0 -1 Mshowa
grestore
grestore
0 0 moveto
1 0 lineto
1 0.618034 lineto
0 0.618034 lineto
closepath
clip
newpath
0 setgray
gsave
gsave
0.004 setlinewidth
0.02381 0 moveto
0.06349 0.15462 lineto
0.10317 0.27055 lineto
0.14286 0.35748 lineto
0.18254 0.42267 lineto
0.22222 0.47154 lineto
0.2619 0.50819 lineto
0.30159 0.53567 lineto
0.34127 0.55628 lineto
0.38095 0.57173 lineto
0.42063 0.58331 lineto
0.46032 0.592 lineto
0.5 0.59851 lineto
0.53968 0.6034 lineto
0.57937 0.60706 lineto
0.61905 0.6098 lineto
0.65873 0.61186 lineto
0.69841 0.61341 lineto
0.7381 0.61456 lineto
0.77778 0.61543 lineto
0.81746 0.61608 lineto
0.85714 0.61657 lineto
0.89683 0.61694 lineto
0.93651 0.61721 lineto
0.97619 0.61742 lineto
stroke
grestore
grestore
% End of Graphics
MathPictureEnd
:[font = text; inactive; ]
When approaching a constant breakpoint value A1 given an initial value A0 at time 0, the formula to use is like the one above, but adding in the initial value A0, and using A1-A0 (the amplitude difference) in place of A:
[(Exp glide from A0=1 to A1=2)] 0.5 0.62428 0 -1 Msboxa
[(5)] 0.25352 -0.0125 0 1 Msboxa
[(10)] 0.48323 -0.0125 0 1 Msboxa
[(15)] 0.71294 -0.0125 0 1 Msboxa
[(20)] 0.94265 -0.0125 0 1 Msboxa
[(time\(sec\))] 1.00625 0 -1 0 Msboxa
[(0.25)] 0.01131 0.07725 1 0 Msboxa
[(0.5)] 0.01131 0.15451 1 0 Msboxa
[(0.75)] 0.01131 0.23176 1 0 Msboxa
[(1)] 0.01131 0.30902 1 0 Msboxa
[(1.25)] 0.01131 0.38627 1 0 Msboxa
[(1.5)] 0.01131 0.46353 1 0 Msboxa
[(1.75)] 0.01131 0.54078 1 0 Msboxa
[(2)] 0.01131 0.61803 1 0 Msboxa
[()] 0.02381 0.62428 0 -1 Msboxa
[ -0.001 -0.00725 0 0 ]
[ 1.001 0.61903 0 0 ]
] MathScale
% Start of Graphics
1 setlinecap
1 setlinejoin
newpath
%%Object: Graphics
[ ] 0 setdash
0 setgray
0 setgray
[(Exp glide from A0=1 to A1=2)] 0.5 0.62428 0 -1 Mshowa
gsave
gsave
0.002 setlinewidth
0 0 moveto
1 0 lineto
stroke
0.25352 -0.00625 moveto
0.25352 0.00625 lineto
stroke
0 setgray
[(5)] 0.25352 -0.0125 0 1 Mshowa
0.48323 -0.00625 moveto
0.48323 0.00625 lineto
stroke
0 setgray
[(10)] 0.48323 -0.0125 0 1 Mshowa
0.71294 -0.00625 moveto
0.71294 0.00625 lineto
stroke
0 setgray
[(15)] 0.71294 -0.0125 0 1 Mshowa
0.94265 -0.00625 moveto
0.94265 0.00625 lineto
stroke
0 setgray
[(20)] 0.94265 -0.0125 0 1 Mshowa
0 setgray
[(time\(sec\))] 1.00625 0 -1 0 Mshowa
0.02381 0 moveto
0.02381 0.61803 lineto
stroke
0.01756 0.07725 moveto
0.03006 0.07725 lineto
stroke
0 setgray
[(0.25)] 0.01131 0.07725 1 0 Mshowa
0.01756 0.15451 moveto
0.03006 0.15451 lineto
stroke
0 setgray
[(0.5)] 0.01131 0.15451 1 0 Mshowa
0.01756 0.23176 moveto
0.03006 0.23176 lineto
stroke
0 setgray
[(0.75)] 0.01131 0.23176 1 0 Mshowa
0.01756 0.30902 moveto
0.03006 0.30902 lineto
stroke
0 setgray
[(1)] 0.01131 0.30902 1 0 Mshowa
0.01756 0.38627 moveto
0.03006 0.38627 lineto
stroke
0 setgray
[(1.25)] 0.01131 0.38627 1 0 Mshowa
0.01756 0.46353 moveto
0.03006 0.46353 lineto
stroke
0 setgray
[(1.5)] 0.01131 0.46353 1 0 Mshowa
0.01756 0.54078 moveto
0.03006 0.54078 lineto
stroke
0 setgray
[(1.75)] 0.01131 0.54078 1 0 Mshowa
0.01756 0.61803 moveto
0.03006 0.61803 lineto
stroke
0 setgray
[(2)] 0.01131 0.61803 1 0 Mshowa
0 setgray
[()] 0.02381 0.62428 0 -1 Mshowa
grestore
grestore
0 0 moveto
1 0 lineto
1 0.618034 lineto
0 0.618034 lineto
closepath
clip
newpath
0 setgray
gsave
gsave
0.004 setlinewidth
0.02381 0.30902 moveto
0.06349 0.38633 lineto
0.10317 0.44429 lineto
0.14286 0.48776 lineto
0.18254 0.52035 lineto
0.22222 0.54479 lineto
0.2619 0.56311 lineto
0.30159 0.57685 lineto
0.34127 0.58716 lineto
0.38095 0.59488 lineto
0.42063 0.60067 lineto
0.46032 0.60502 lineto
0.5 0.60827 lineto
0.53968 0.61071 lineto
0.57937 0.61255 lineto
0.61905 0.61392 lineto
0.65873 0.61495 lineto
0.69841 0.61572 lineto
0.7381 0.6163 lineto
0.77778 0.61673 lineto
0.81746 0.61706 lineto
0.85714 0.6173 lineto
0.89683 0.61749 lineto
0.93651 0.61762 lineto
0.97619 0.61773 lineto
stroke
grestore
grestore
% End of Graphics
MathPictureEnd
:[font = text; inactive; endGroup; endGroup; ]
A Music Kit envelope is put together by concatenating segments such as the above Actually, the segments are not exactly concatenated. The asymptote gets changed each breakpoint, so the envelope just starts approaching the new asymptote from wherever it happens to be.
:[font = section; inactive; startGroup; ]
Sampled Exponentials
:[font = subsection; inactive; startGroup; ]
Replacing t by n T
:[font = text; inactive; ]
A digitally sampled exponential is obtained by replacing t with n T, where n is the sample number and T is the sampling interval in seconds. The sampling-rate is denoted fs. Thus,
Note that Exp[-n T / tau] can be written as Exp[-T/tau]^n.
Define
:[font = input; ]
Clear[A,T,tau,g];
g = Exp[-T/tau];
:[font = text; inactive; ]
Now our basic exponential looks like
:[font = input; startGroup; ]
f1d[n] = A g^n
:[font = output; inactive; output; endGroup; ]
A/E^((T*n)/tau)
;[o]
A
----------
(T n)/tau
E
:[font = text; inactive; ]
This is the function used by the AsympUG unit generator to compute the samples of an exponential. Note that it can be computed recursively via f1d[n] = g f1d[n-1]. The form f1d[n] = g f1d[n-1] + (1-g) A gives an exponential approach to the value A (show this!). This latter recursion is what the AsympUG DSP code actually does each sample, except that it uses the variable r = 1-g = 1-Exp[-T/tau], called the "rate". Thus, the recursion used by asymp.asm is
f1d[n] = (1-r) f1d[n-1] + r A
to approach the value A exponentially with time-constant tau, where r = 1-Exp[-T/tau].
The rate is so named because when it is at its maximum value of 1 (tau = 0), A is reached in one sample step, as substitution in the above recurrence immediately shows. When r is at its minimum value of 0 (tau = infinity), the exponential never gets to the asymptote A. The source code for AsympUG is in /usr/lib/dsp/ugsrc/asymp.asm.
Instead of t60, the Music Kit uses "t48" which is the time it takes to decay -48 dB from the initial starting value (in the case of final decay toward zero).
:[font = input; startGroup; ]
t48taus = -N[Log[10^(-48/20)]] (* "t48" in time-constants *)
:[font = output; inactive; output; ]
5.52620422318571
;[o]
5.5262
:[font = text; inactive; ]
Thus, 5.53 time-constants is about 48 dB of decay.
:[font = input; startGroup; ]
-20 Log[10, Exp[ - t48taus tau / tau]] (* test it out *)
Recall that smoothness is defined as the number of breakpoint intervals to reach the next breakpoint value. In this context, "reaching" the next breakpoint value is defined as traversing t48 seconds along the exponential which is approaching that breakpoint value. Let d denote the time between the current breakpoint and the next breakpoint. Then, for S=1, d must be t48. For S=2, we want d to be 1/2 of t48, and so on. Thus, the fundamental relation relating smoothness to exponential decay is
d S = t48
We found above that t48 was about 5.5 time constants. Thus,
d S = 5.5262 tau
(to 4 digits). Since tau is related to r by the formula r = 1-g = 1-Exp[-T/tau], we can solve for the AsympUG rate r in terms of S to get
r = 1 - Exp[-5.5262 / (d S)]
where d is the distance to the next breakpoint in seconds, and S is the smoothing parameter. Since the default value for S is 1, we see that increasing it to 2 corresponds to taking the square root of g so that r goes from 1-g to 1-Sqrt[g]. Since g is between 0 and 1, Sqrt[g] is always larger than g, so r (the "rate") is made smaller by increasing smoothness. This actually makes sense!
:[font = subsection; inactive; ]
What AsympUG Does
:[font = text; inactive; ]
As mentioned above, AsympUG generates an exponential envelope segment using the recursion
f1d[n] = g f1d[n-1] + r A
Let's demonstrate this recursion for the following example: